﻿using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using DimensionsHelper.CommandLine.ConsoleTracer;
using DimensionsHelper.Common.Contracts.Metadata;
using DimensionsHelper.Common.Metadata.Axis;
using DimensionsHelper.Common.Utils;
using DimensionsHelper.LanguageService.Serialization;
using DimensionsHelper.Services.Metadata;

namespace DimensionsHelper.CommandLine.PreTableRender;

public partial class HtmlTableRender
{
    private const string RunScriptFile = "Run@Html.mrs";
    private const string MetadataTemp = "Metadata@Temp.mrs";
    private const string HtmlExportName = "Table@Html.html";
    private const string BasicHeaderFormat =
        "axis({{base() [Ishidden=True], T 'Total' expression('{0}')}}) as banner ''";

    private readonly Dictionary<string, AxisExpression> _axes;
    private readonly Encoding _encoding;

    private readonly CodeGenerator _generator = new();
    private readonly PreTableOptions _options;
    private readonly List<string> _tempFiles;
    private AxisExpression? _currentAxis;
    private MetadataDocument? _metadata;

    public HtmlTableRender(PreTableOptions options)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        _encoding = Encoding.GetEncoding(options.Encoding);
        _options = options;
        MetadataName = options.AsAbsolutePath(options.Metadata);
        HtmlName = options.AsAbsolutePath(HtmlExportName);
        _tempFiles = [];
        _axes = [];
    }


    /// <summary>
    ///     当前使用的元数据路径。
    ///     在<see cref="Apply" />方法调用前后，此值可能会不一样。
    /// </summary>
    private string MetadataName { get; set; }

    /// <summary>
    ///     输出的 html 格式的临时文件名。
    /// </summary>
    public string HtmlName { get; }


    private void GenerateMergedFiles(string target, params string[] files)
    {
        FileHelper.DeleteFile(target);
        using FileStream fileStream = File.Create(target);
        using var writer = new StreamWriter(fileStream, _encoding);
        foreach (string file in files)
        {
            using var reader = new StreamReader(file, _encoding);
            writer.WriteLine(reader.ReadToEnd());
        }
    }


    // With.Default
    //     .CellItems.Clear()
    //     .CellItems.AddNew(0,0) '(0,0) Means showing Count and Decimals=0
    //     .Properties["ShowPercentSigns"] = True
    //     .Properties["ShowPercentSigns"] = True
    //     .Properties["ZeroCountSymbol"] = "0" 'If the count is 0, it will show "-"
    //     .Properties["ZeroPercentSymbol"] = "0" 'If the % is 0, it will show "-"
    //     .Properties["RoundZeroCountSymbol"] = ">" 'If the count is rounded to 0, it will show "-"
    //     .Properties["RoundZeroPercentSymbol"] = ">" 'If the % is rounded to 0, it will show "*", usually we show ">" in QTM
    //     .Properties["Show100Percent"] = False 'If TRUE, show 100% on baseline
    //     .Properties["ShowPercentSigns"] = False
    // End With

    private void BuildCellItemsScript()
    {
        // With .Default
        _generator.StartWithContext(".Default");
        // .CellItems.Clear()
        _generator.WriteLine(".CellItems.Clear()");
        // .CellItems.AddNew(0,0)
        _generator.WriteLine(".CellItems.AddNew(0,0)");
        // .Properties["ZeroCountSymbol"] = "0"
        _generator.WriteLine(".Properties[\"ZeroCountSymbol\"] = \"0\"");
        // .Properties["ZeroPercentSymbol"] = "0"
        _generator.WriteLine(".Properties[\"ZeroPercentSymbol\"] = \"0\"");
        // .Properties["RoundZeroCountSymbol"] = ">"
        _generator.WriteLine(".Properties[\"RoundZeroCountSymbol\"] = \">\"");
        // .Properties["RoundZeroPercentSymbol"] = ">"
        _generator.WriteLine(".Properties[\"RoundZeroPercentSymbol\"] = \">\"");
        // .Properties["Show100Percent"] = False
        _generator.WriteLine(".Properties[\"Show100Percent\"] = False");
        // .Properties["ShowPercentSigns"] = False
        _generator.WriteLine(".Properties[\"ShowPercentSigns\"] = False");
        // End With
        _generator.EndContext();
    }


    // With .Exports
    //    With .mrHtmlExport
    //        .Properties["OverwriteOutput"]= True
    //        .Properties["DisplayOption"] = "Table Only"
    //        .Properties["DisplayLogo"] = False
    //        .Properties["UseStyles"] = False
    //        .Export(OUTPUT_FOLDER + OUTPUT_NAME + ".htm")
    //    End With
    // End With

    private void BuildHtmlExportScript()
    {
        _generator.StartWithContext(".Exports.mrHtmlExport");
        _generator.WriteLine(".Properties[\"OverwriteOutput\"]= True");
        _generator.WriteLine(".Properties[\"DisplayOption\"] = \"Table Only\"");
        _generator.WriteLine(".Properties[\"DisplayLogo\"] = False");
        _generator.WriteLine(".Properties[\"DisplayTitle\"] = False");
        _generator.WriteLine(".Properties[\"DisplayLogo\"] = False");
        _generator.WriteLine(".Properties[\"UseStyles\"] = False");
        _generator.WriteLine(".Properties[\"IncludeTOC\"] = False");

        string table = _options.AsAbsolutePath(HtmlExportName);
        _tempFiles.Add(table);
        _generator.WriteCallExpression(".Export", [$"\"{table}\""]);
        _generator.EndContext();
    }


    private MetadataDocument GetMetadataDocument()
    {
        if (_metadata != null)
        {
            return _metadata;
        }

        _metadata = new MetadataDocument();
        _metadata.Open(_options.AsAbsolutePath(_options.Metadata));

        return _metadata;
    }


    private void DisposeMetadataDocument()
    {
        if (_metadata == null)
        {
            return;
        }

        _metadata.Close();
        _metadata = null;
    }


    private IField GetField(string fieldName)
    {
        // LoopVar[{Code}].Slice[[{Code}].Slice]
        string[] levelNames = FieldSplitRegex().Split(fieldName);
        IField? mdmField = null;
        foreach (var name in levelNames)
        {
            if (mdmField != null)
            {
                if (mdmField is IArray array && array.Class.Fields.TryGetByKey(name, out IField? subField))
                {
                    mdmField = subField;
                }
                else if (mdmField is IBlock block && block.Fields.TryGetByKey(name, out IField? blockSubField))
                {
                    mdmField = blockSubField;
                }
                else
                {
                    throw new Exception($"[HtmlTableRender] 字段'{mdmField.FullName}'不包含名为'{name}'的子字段。");
                }
            }
            else
            {
                if (GetMetadataDocument().Fields.TryGetByKey(name, out IField? field))
                {
                    mdmField = field;
                }
                else
                {
                    throw new Exception($"[HtmlTableRender] 未找到名为'{name}'的MDM字段。");
                }
            }
        }

        return mdmField!;
    }


    private static IEnumerable<IElement> IterateCategoryRange(IField field, AxisElement element)
    {
        Debug.Assert(element.Type == AxisElementType.CategoryRange);

        bool isRangeStart = false;
        foreach (IElement cat in field.Categories)
        {
            if (!isRangeStart)
            {
                isRangeStart = element.LowerBound.Equals(cat.Name, StringComparison.OrdinalIgnoreCase);
            }

            if (isRangeStart || string.IsNullOrEmpty(element.LowerBound))
            {
                yield return cat;
            }

            if (element.UpperBound.Equals(cat.Name, StringComparison.OrdinalIgnoreCase))
            {
                break;
            }
        }
    }


    private static void GetAllCategoryNames(List<string> names, AxisElement element)
    {
        switch (element.Type)
        {
            case AxisElementType.Category:
                names.Add(element.Name!);
                break;

            case AxisElementType.Net:
            case AxisElementType.Combine:
                foreach (AxisElement item in element.Elements)
                {
                    GetAllCategoryNames(names, item);
                }

                break;
        }
    }


    private static string GetCategoryLabel(IField field, string name)
    {
        var element = field.Elements.Find(name);

        if (element != null)
        {
            return BuildCurlyDescription(
                element.Label.GetText() ?? element.Name,
                element.Name);
        }

        return name;
    }


    private void ExpandAxisElement(IAxisExclusive target, AxisElement source, IField field)
    {
        switch (source.Type)
        {
            case AxisElementType.Category:
                if (!string.IsNullOrEmpty(source.Name))
                {
                    if (source.IsExclude)
                    {
                        target.ExcludeNames(source.Name);
                    }
                    else
                    {
                        AxisElement cat =
                            AxisElementFactory.CreateCategory(source.Name, GetCategoryLabel(field, source.Name));
                        cat.CopySuffixes(source);
                        target.Append(cat);
                    }
                }

                break;

            // 分类区间，展开列表
            case AxisElementType.CategoryRange:
                foreach (IElement cat in IterateCategoryRange(field, source))
                {
                    if (source.IsExclude)
                    {
                        target.ExcludeNames(cat.Name);
                    }
                    else
                    {
                        target.Append(
                            AxisElementFactory.CreateCategory(cat.Name, GetCategoryLabel(field, cat.Name)));
                    }
                }

                break;

            // net/combine 包含下级列表，需要递归
            case AxisElementType.Net:
            case AxisElementType.Combine:
                // 忽略跳过的元素
                if (!source.IsExclude)
                {
                    AxisElement e = source.Type == AxisElementType.Net
                        ? _currentAxis!.Factory.CreateNet(source.Name, source.Description ?? source.Name)
                        : _currentAxis!.Factory.CreateCombine(source.Name, source.Description ?? source.Name);

                    target.Append(e);

                    foreach (AxisElement element in source.Elements)
                    {
                        if (!element.IsExclude)
                        {
                            ExpandAxisElement(e, element, field);
                        }
                    }

                    // combine/net 时，将次级码号添加到描述中
                    if (source.Type == AxisElementType.Combine)
                    {
                        var names = new List<string>();
                        GetAllCategoryNames(names, e);
                        e.Description += "${" + string.Join(',', names) + "}";
                    }
                }

                break;
        }
    }


    private AxisExpression FormatAxis(AxisExpression axis, string fieldName)
    {
        var expr = new AxisExpression();
        var field = GetField(fieldName);
        _currentAxis = expr;

        // 遇到带有 .. 的区间，将区间扩展，同时，需要排除在区间后单独列出的分类
        foreach (AxisElement element in axis)
        {
            ExpandAxisElement(expr, element, field);
        }

        return expr;
    }


    private static void ExpandInnerList(IElements list, Action<IElement> callback)
    {
        foreach (var element in list)
        {
            var reference = element.Reference;
            if (reference != null)
            {
                ExpandInnerList(reference.Elements, callback);
            }
            else
            {
                callback(element);
            }
        }
    }


    private string BuildSideAxis(PreFieldOptions field, string fieldName)
    {
        string axis = "";

        if (!string.IsNullOrEmpty(field.Axis))
        {
            try
            {
                AxisExpression expr = FormatAxis(AxisExpression.Parse(field.Axis), fieldName);
                _axes.SetValue(fieldName, expr);
                axis = expr.ToString();
            }
            catch (Exception e)
            {
                Console.WriteLine($@"[AxisParser] {e.Message}");
            }
        }
        else
        {
            var axisExpr = new AxisExpression();
            // 添加所有Code
            var expr = axisExpr;
            ExpandInnerList(GetField(fieldName).Elements, element =>
            {
                if (element.ElementType == ElementType.Category)
                {
                    expr.AppendCategory(element.Name,
                        BuildCurlyDescription(element.Label.GetText() ?? element.Name, element.Name));
                }
            });
            // foreach (IElement element in GetField(fieldName).Categories)
            // {
            //     axisExpr.AppendCategory(element.Name,
            //         BuildCurlyDescription(element.Label.GetText() ?? element.Name, element.Name));
            // }

            // ^Codes...
            foreach (string code in field.IgnoreCodes)
            {
                axisExpr.ExcludeNames(code);
            }

            // [IsFixed=True]
            foreach (string code in field.FixedCodes)
            {
                axisExpr.AppendCategory(code)
                    .SetSuffix(AxisElementSuffixType.IsFixed, true);
            }

            axisExpr = FormatAxis(axisExpr, fieldName);
            _axes.SetValue(fieldName, axisExpr);
            axis = axisExpr.ToString();
        }

        return axis;
    }


    private static string BuildCurlyDescription(string left, string curlyInner)
    {
        return $"{left}${{{curlyInner}}}";
    }


    private SortOrders GetTableSortOrder(PreFieldOptions fieldOptions)
    {
        return fieldOptions.SortOrder == SortOrders.None ? _options.SortOrder : fieldOptions.SortOrder;
    }


    private string GetHeaderExpression(PreFieldOptions fieldOptions)
    {
        string? expr = fieldOptions.HeaderExpression;

        if (string.IsNullOrEmpty(expr))
        {
            expr = _options.HeaderExpression;
        }

        return string.IsNullOrEmpty(expr) ? "Respondent.Serial > 0" : expr;
    }


    private void BuildTableScript(PreFieldOptions table, int index)
    {
        string header = string.Format(BasicHeaderFormat, GetHeaderExpression(table));
        string tableName = "Table" + index;
        string tableLabel = BuildCurlyDescription(tableName, table.Name);
        // With .AddNew()
        _generator.StartWithContext(["", "AddNew"],
        [
            $"\"{tableName}\"",
            $"\"{table.FieldName}{BuildSideAxis(table, table.FieldName)}*{header}\"",
            $"\"{tableLabel}\""
        ]);
        // .Side.SubAxes[0].Label = strTitleText
        _generator.WriteAssignment(false, ".Side.SubAxes[0].Label", $"\"{tableLabel}\"");
        // Sort
        SortOrders order = GetTableSortOrder(table);
        if (order != SortOrders.None)
        {
            // .{TableName}.SortColumn = "first"
            _generator.WriteAssignment(false, ["", "SortColumn"], "\"first\"");
            if (order == SortOrders.Asc)
            {
                _generator.WriteAssignment(false, ["", "SortColumn", "Order"], "0");
            }
        }

        // Filter
        if (!string.IsNullOrEmpty(table.Filter))
        {
            // .{TableName}.Filters.AddNew(filter_name, filter_expr, "")
            _generator.WriteCallExpression(".Filters.AddNew", ["\"f0\"", $"\"{table.Filter}\"", ""]);
        }

        // Remove empty row
        if (table.RemoveEmptyRow)
        {
            _generator.WriteLine(".Rules.AddNew()");
        }

        _generator.EndContext();
    }


    private void BuildTablesScript()
    {
        // With .Tables
        _generator.StartWithContext(".Tables");

        int i = 0;
        foreach (PreFieldOptions field in _options.Fields)
        {
            BuildTableScript(field, i++);
        }

        // End With
        _generator.EndContext();
    }


    private void BuildRunScript(Stream fileStream)
    {
        _generator.Clear();
        _generator.WriteSingleLineComment("生成的代码，请勿修改。");
        //_generator.IncludeFile(_options.AsAbsolutePath("job.ini"));
        //_generator.IncludeFile(_options.AsAbsolutePath("Library\\Library.mrs"));
        _generator.DeclareVariables("TableDoc", "StartTime", "EndTime");
        // includes
        if (_options.RenderIncludes.Count > 0)
        {
            foreach (string inc in _options.RenderIncludes)
            {
                _generator.IncludeFile(_options.AsAbsolutePath(inc));
            }
        }

        // TableDoc
        _generator.WriteCreateObject("TableDoc", "TOM.Document");
        _generator.DeclareVariable("MDM");
        _generator.WriteCreateObject("MDM", "MDM.Document");

        // 添加 Manipulation
        if (!string.IsNullOrEmpty(_options.Manipulation))
        {
            // MDM.Open("Metadata")
            _generator.WriteCallExpression(["MDM", "Open"], [MetadataName]);
            _generator.IncludeFile(_options.AsAbsolutePath(_options.Manipulation));

            string manipulated = Path.Combine(
                Path.GetDirectoryName(MetadataName)!,
                Path.GetFileNameWithoutExtension(MetadataName) + "_Manipulated.mdd");

            // MDM.Save("Metadata")
            _generator.WriteCallExpression(["MDM", "Save"], [manipulated]);

            _tempFiles.Add(manipulated);
            MetadataName = manipulated;
        }

        // 添加字段定义文件
        if (_options.FieldIncludes.Count > 0)
        {
            string joined = Path.Combine(
                Path.GetDirectoryName(MetadataName)!,
                Path.GetFileNameWithoutExtension(MetadataName) + "_Temp.mdd");

            string includeMetadataFile = _options.AsAbsolutePath(_options.FieldIncludes[0]);
            // sbAddMetadata("metadata","targetMetadata","sbMetadata.mrs")
            if (_options.FieldIncludes.Count > 1)
            {
                // 多个文件合并
                includeMetadataFile = _options.AsAbsolutePath(MetadataTemp);
                _tempFiles.Add(includeMetadataFile);
                GenerateMergedFiles(includeMetadataFile, [.. _options.FieldIncludes]);
            }

            _generator.WriteCallExpression(["sbAddMetadata"], [MetadataName, joined, includeMetadataFile]);

            _tempFiles.Add(joined);
            MetadataName = joined;
        }

        // TableDoc.DataSet.Load(metadata,,caseData,"mrDataFileDsc")
        _generator.WriteCallExpression("TableDoc.DataSet.Load",
            [$"\"{MetadataName}\"", "\"\"", $"\"{_options.AsAbsolutePath(_options.CaseData)}\"", "\"mrDataFileDsc\""]);
        // With TableDoc
        _generator.StartWithContext("TableDoc");
        // .DataSet.Description = "description"
        _generator.WriteAssignment(false, ".DataSet.Description", "\"Html Render Table\"");
        for (int i = 0; i < 8; i++)
        {
            _generator.WriteLine($".Default.Annotations[{i}].Specification = \"\"");
        }

        // .Default.Properties["RoundingOptions"] = 1
        _generator.WriteLine(".Default.Properties[\"RoundingOptions\"] = 1");
        // .Global.Filters.AddNew("GlobalFilter",GLOBAL_FILTER,"")
        if (!string.IsNullOrEmpty(_options.GlobalFilter))
        {
            _generator.WriteCallExpression(".Global.Filters.AddNew", ["\"GlobalFilter\"", $"\"{_options.GlobalFilter}\"", "\"\""]);
        }

        // CellItems
        BuildCellItemsScript();
        // Tables
        BuildTablesScript();
        // .Populate()
        _generator.WriteLine(".Populate()");
        // .Export
        BuildHtmlExportScript();
        // End With
        _generator.EndContext();
        // 
        _generator.Generate(fileStream, _encoding);
    }


    /// <summary>
    ///     删除所有中间文件
    /// </summary>
    public void ClearTempFiles()
    {
        foreach (string file in _tempFiles)
        {
            FileHelper.DeleteFile(file);
        }
    }


    /// <summary>
    ///     应用表格配置并生成文件
    /// </summary>
    public void Apply()
    {
        string runnerPath = _options.AsAbsolutePath(RunScriptFile);
        _tempFiles.Add(runnerPath);

        using (FileStream file = File.Create(runnerPath))
        {
            BuildRunScript(file);
            file.Close();
        }
        
        DisposeMetadataDocument();

        var cmd = new Process()
        {
            StartInfo =
            {
                FileName = "cmd.exe",
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardError = true,
                RedirectStandardInput = true,
                RedirectStandardOutput = true
            }
        };

        try
        {
            cmd.Start();
            cmd.StandardInput.WriteLine($"mrScriptCL {runnerPath}  /d:PRE_TABLE_RENDER=True");
            cmd.StandardInput.Flush();
            cmd.StandardInput.Close();
            cmd.WaitForExit();
            // output
            string output = cmd.StandardOutput.ReadToEnd();
            if (!string.IsNullOrEmpty(output) && _options.Print)
            {
                Tracer.Log(output);
            }
        }
        catch (Exception e)
        {
            Tracer.Error(e.Message);
        }
        finally
        {
            // error
            string error = cmd.StandardError.ReadToEnd();
            if (!string.IsNullOrEmpty(error))
            {
                Tracer.Error(error);
            }

            cmd.Close();
            cmd.Dispose();
        }
    }

    [GeneratedRegex(@"(\[.*?\]\.)|((?<=[a-zA-Z0-9_@#$])\.{1}(?=[a-zA-Z_@#$]))")]
    private static partial Regex FieldSplitRegex();
}